From PARADIS@htu.tu-graz.ac.at Fri Mar 25 08:41:08 1994 
        The Bloody SPC-700 
        ------------------ 

A try to stumble into the inner secret of a nasty chip. 
By Antitrack exclusively for the FAMIDEV development group. 

Chapter 1: 
---------- 

FACTS 
* The SPC 700 is a very stupid sound chip with about the worst handling 
  that you have seen in your lifetime. 
* This chip is a co processor. He has a quite large instruction set 
  (contrary to the Amiga's COPPER, who has a very small one) and 64KB 
RAM memory, of which you can use atleast 32KB.  (or so) 
* All program and data that is supposed to be run by this chip must 
be'  moved to the SPC's own ram with a small loop that pokes each byte of 
  your SPC assembler program and (e.g. sample-)data into four memory 
  locations : $2140 - $2143. They are your only chance to communicate 
with  the SPC. 
* These four memory locations have different meanings for read and 
write; 
  if you read (LDA) $2140, you get the data from memory loc. 00f4 (or 
so)  of the sound chip. 
* On power-on, the SPC 700 jumps (much like the main processor) to a 
very  small ROM area that resides from $ffc0 to $ffff inside the SPC. 
  (This chip REALLY follows the black box principle, eh...) This 
program  at $ffc0 is waiting to get the data in the right format on his 
input ports  at $00f4/5/6/7 , which are $2140/1/2/3 from the 65c816's (e.g. 
your's )   point of view. 
* Your main program will therefore have to follow the SPC's 
conditions and  poke all the program and data for the SPC into 2140/1/2/3 in a 
special  order. 
* When transmission is completed, you will also have transmitted the 
start  address of your SPC code, and the SPC will start to execute your 
program  there. 


--------------------QUESTIONS. 

Q: How do I move my program and data to the SPC then, what format do 
I have to use? 

A: First, your SPC data/code has to be moved from ROM to the extra 
RAM at e.g. $7f0000 . Dont ask me why it has to be in RAM, probably it doesnt 
but all the existing routines that send data to the SPC do something like 
that. 
Your data/code has to be in groups which I will call "chunks". A 
alid chunk looks like that: 
first word: number of bytes to transmit to SPC           -+ 
sec. word : start address where to move data to the SPC   |  one chunk 
byte 4-???? : your data/code                             -+ 
You can have as many chunks as you want to , but the last chunk must 
be like that: 
first word : $0000 
second word: Start address of your code. 

Q: So if you are right, this means: After I transmitted all my code 
and data, and my own SPC code takes over the control, I might encounter 
problems if my SPC program has to communicate with the outer world (the 
65c816).What if the main program wants to change sounds? What if a background 
melody shall always play on two voices, and extra two voices will be used for 
sound effects whenever the player sprite e.g. picks up an object? 
A: That is sure a point. Your own code will have to look at memory 
locations $00f4/00f5/00f6/00f7 , because they are the only accessible from 
outside at $2140/1/2/3. The easiest way would be: As soon as any of $f4-$f7 
change, jump into the Boot ROM at $ffc0 (?) so the SPC is executing his 
receive routine again. Then you *probably* can send another SPC chunk with new 
sound and code to the SPC.... 
Q: This only helps if a complete new tune is to be played, this 
doesnt help if a melody using two voices shall still remain.... 
A: Thats true. The best approach is to send own command bytes to the 
SPC and your SPC code has to check out $f4-$f7 constantly and react to it..... 
A command byte like $00 could mean: sound off, 
                    $01           : play tune 1 
                    . 
                    . 
                    . 
                    $0f          : play tune $0f 
                    $10           : play jingle (fx) 01 
                    . 
                    . 
                    . 
                    $ff           : jump to $ffc0 (??) the receive 
ROM routine 


Q: is there another approach? 
A: Yes there is. As you probably know, all important addresses of the 
SPC 700 reside inside its own RAM's zeropage: 
Address         / register          / usage 
0000            Volume left 
0001            Volume right 
0002            Pitch low 
0003            Pitch high          (The total 14 bits of pitch 
height) 
0004            SRCN                Designates source number from 0- 
255 
0005            ADSR 1 
0006            ADSR 2 
0007            GAIN            Envelope can be freely designated by 
your code 
0008            ENVX            Present val of envelope with DSP 
rewrites 
0009            VALX            Present wave height val 
(and so on...) 
Your approach would be to move only sample data there, and/or (lots 
of) very 
small chunks of data with a target address in the zeropage, and a 
starting 
address of e.g. $ffc0. The small chunks would access zeropage 
addresses e.g. 
for the volume etc and thus result in tones; if this is done every 
frame 
you might end up with a music player quite similar to the C64 styled 
ones. 

Q: So anyway, in what format exactly do I have to move data to the 
SPC? 
A: I have the following source code for you, but let me explain it a 
bit 
BEFORE you start to dig into it. 
I've already mentioned the general "chunk" format. The loop does the 
following: 

- move ram destination address to $2142/3 (akku: 16 bit) 
- move either #$00 or #$01 into 2141, this depends if you have more 
than $0100 
  bytes of data for the SPC; 
- first time (first chunk you transmit): move constant #$cc into 2140 
- loop: poke each byte that you want to be transmitted into 2140 
(word) 
  the higher 7-15 bits of your accu-word contain the number of bytes 
already 
  moved (e.g. 00 on the start) 
- cmp $2140 with this number of bytes already moved (lower 8 bits of 
this 
  number only!) and wait if its not equal. 
- until the loop is over. 
- for the next chunk header this is repeated, but not #$cc is moved 
into 
  2140 but "nn" (lobyte of number of bytes moved) +3 or +6 if it was 
00 when 
  +3 was used. 
EXAMPLE: 
        move #$0400 to 2142 /word access 
        move #$01 to 2141 
        move #$cc to 2140 
        move "gg00" to 2140 where "gg" is the first real code/data 
byte for 
                            the SPC 
        wait till 2140 is #$00 
        move hh01 to 2140 where "hh" is the second byte of code or 
data for SPC 
        wait till 2140 is #$01 
        move ii02 to 2140 where "ii" is the 3rd byte of data for the 
SPC.... 
        wait till 2140 is #$02 

       lets say "ii" was the last byte. Now we add #$04 (3+carry) to 
#$02 
       (#$02 being the number-1 of how many bytes we moved to the 
SPC), we 
       will push it onto the stack), now : 
       fetch the next header , poke target RAM address into $2142 
(word) 
       poke 00 or 01 into 2141 depending of how many bytes to send, 
       poke #$06 into 2140 (06 : number of bytes sent from last chunk- 
1 + 3 ) 

I think I got this scheme pretty much right this time. Now, is PLEASE 
someone 
going to donate their home-brewed SPC dis/assemblers to me? Oh pretty 
please, 
I hate silent SNES's !  :) 

Source code follows, reassembled from a PAN/Baseline demo "xmas wish 
92/93": 
---------------------------------------------------------------------- 
------ 

; entry to the code starts here 

            SEP #$30       ; x y a set to 8 bit length 
            LDA #$FF       ; ff into audio0w (write) 
            STA $2140 
            REP #$10       ; x,y: 16 bit length 
            LDX #$7FFF 
l0DB5B      LDA $018000,X  ; move rom music data to ram at $7f0000 
            STA $7F0000,X 
            LDA $028000,X  ; move rom music data to ram at $7f0000 
            STA $7F8000,X 
            DEX 
            BPL l0DB5B 
            LDA #$80       ; screen on , probably not important at all 
            STA $2100 
            LDA #$00       ; 00fd/00fe/00ff point to the data that is 
now 
            STA $00FD      ; in ram at $7f0000 
            LDA #$00 
            STA $00FE 
            LDA #$7F 
            STA $00FF 
            STZ $4200      ; disable nmi and timer h/v count 
            SEI            ; disable irq 
            JSR l0DBCD     ; unknown sub routine, labeled "RESTART" 
by PAN/ATX 
            SEP #$30       ; all regs 8 bit 
l0DB8B      LDA $2140      ; wait for reply from sound chip ? 
            BNE l0DB8B 
            LDA #$E0       ; audio3w ? 
            STA $2143 
            LDA #$FF       ; send data to sound chip ? 
            STA $2142      ; $ffe0 this could be an address within the 
                           ; sound chip ROM between $ffc0 and $ffff 
in the 
                           ; ROM mask....... 
            LDA #$01       ; send data to sound chip ? 
            STA $2141 
            LDA #$01       ; send data to sound chip ? 
            STA $2140 
l0DBA4      LDA $2140      ; wait for reply from sound chip ? 
            CMP #$01       ; what a fuck of a protocol .... :( 
            BNE l0DBA4 
l0DBAB      LDA $2140      ; wait again for reply from soundchip ? 
            CMP #$55 
            BNE l0DBAB 
            LDA $0207       ; aha ... move $0207 to sound chip ? 
            STA $2141       ; probably sound number selector 
            LDA #$07 
            STA $2140       ; send data to sound chip 
l0DBBD      LDA $2140       ; wait until sound chip accepted data? 
            CMP #$07 
            BNE l0DBBD 
l0DBC4      LDA $2140       ; wait for reply ? 
            CMP #$55 
            BNE l0DBC4 
            CLI 
            RTS 
l0DBCD      PHP            ; labeled "RESTART" by pan/ATX 
            JSR l0DBD8     ; 
            PLP 
            LDA #$00       ; 00 into audio0w 
            STA $2140 
            RTS 
l0DBD8      PHP 
            REP #$30       ; a,x,y 16 bit regs 
            LDY #$0000     ; needed first time at lda [$fd],y : 
pointer to ram 
            LDA #$BBAA 
l0DBE1      CMP $2140      ; wait for sound chip $2140/2141 ? 
            BNE l0DBE1 
            SEP #$20      ; akku  8 bit 
            LDA #$CC 
            BRA l0DC12    ;  oh well, another mystery  :-) 

; jump here if overflow is set  e.g. if more than $0100 data to move 
l0DBEC      LDA [$FD],Y   ; get data from ram pointer 
            INY           ; the accumulator is about to get "xx00" 
where 
            XBA           ;    /"xx" is the byte from [fd],y (first 
data byte) 
            LDA #$00      ;    /and resides into bit 15-7 of accu, 
and 00 is 
            BRA l0DBFF    ;    /#$00 (8bit number of bytes already 
sent) 

l0DBF4      XBA           ; accu is now "nn??" ?? is old data from 
last loop 
            LDA [$FD],Y   ; accu is now "nnxx" with xx the newest 
data byte 
            INY           ;                                   /for 
the SPC! 
            XBA           ; accu is now "xxnn" 
l0DBF9      CMP $2140     ; wait for sound chip to reply with "nn" !! 
            BNE l0DBF9 
            INC A         ; increment number of bytes that were 
sent... 
                          ; accu is now "xxnn" with newest val for 
nn:=nn+1 
l0DBFF      REP #$20      ; akku 16 bit 
            STA $2140     ; poke "xxnn" to soundchip. xx is actual 
data, 
            SEP #$20      ; akku 8 bit  ! nn is the 8-bit cutted 
number of bytes 
            DEX                         ! which were already sent!! 
            BNE l0DBF4    ; as many times as xreg says... 

l0DC09      CMP $2140     ; byte "nn" will be replied from the SPC if 
data 
            BNE l0DC09    ; received correctly! 
l0DC0E      ADC #$03      ; compare accu with #$fb  ADC WILL ADD #$04 
COZ 
                          ; CARRY IS ALWAYS SET AFTER THE CMP!!! 
ATTENTION! 
            BEQ l0DC0E    ; if accu was $fb then accu := $03 . (what 
for?) 
l0DC12      PHA           ; push value accu+$04 to stack (or 
beginning: #$cc) 
            REP #$20      ; accu = 16 bit 
            LDA [$FD],Y   ; get ram data 2 bytes 
            INY           ; point to next word 
            INY 
            TAX           ; x:=a : number of bytes to transmit 
            LDA [$FD],Y   ; get ram data 
            INY 
            INY 
            STA $2142     ; audio2w  : possibly the dest. area in the 
spc700 
            SEP #$20      ; accu 8 bit 
            CPX #$0100    ; set carry if first ram data was >= 0100 
            lda #$00      ; 
            ROL           ; 
            STA $2141     ; if ram data >= 0100, poke "1" into reg 1 
otherw 0 
            ADC #$7F      ; SET OVERFLOW FLAG IF X>=$0100 !!!! (nice 
trick!) 
            PLA 
            STA $2140     ; $cc in the first case , nn+4 on all later 
cases 
l0DC32      CMP $2140    ; wait for snd chip reply 
            BNE l0DC32 
            BVS l0DBEC   ; if there were more than $0100 data for the 
spc's RAM 
                         ; move them where they R supposed to belong 
to! 
            PLP 
            RTS 

            PLA 
            STA $2140    ; same shit, never been jumped into 
l0DC3F      CMP $2140 
            BNE l0DC3F 
            BVS l0DBF9 
            PLP 
            RTS 





; also lets look at 7f0000: the first few bytes at 7f0000 are: 
7f0000: b7 0e 00 04 20 cd cf bd e8 00 5d af c8 f0 d0 fb 5d d5 00 01 
d5 00 02 
b7 0e should be number of bytes to transmit, 0400 the destination 
inside the 
spc.... 
at this point I really need an SPC dis/assembler.....  :((( 
Okay well my first source was incompetent, sure thing. But I think I 
could 
solve a lot of questions meanwhile. 


This Web Space provided by InReach Internet Communications
InReach is not responsible for material contained on this page.
Last Modified: 15-May-97 06:52
